package top.malaoshi.webmvc;

import top.malaoshi.easy.logging.Log;
import top.malaoshi.easy.logging.LogFactory;
import top.malaoshi.sprout.ioc.DefaultSingletonBeanRegistry;
import top.malaoshi.util.Tuple2;
import top.malaoshi.webmvc.exception.BeanCreationException;
import top.malaoshi.webmvc.info.ClassInfo;
import top.malaoshi.webmvc.info.FieldInfo;
import top.malaoshi.webmvc.info.MethodInfo;
import top.malaoshi.webmvc.utils.ClassUtil;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 初始化bean
 */
public class InitBean {
    static final Log LOG = LogFactory.getLog(InitBean.class);
    DefaultSingletonBeanRegistry registry = DefaultSingletonBeanRegistry.me();
    /**
     * 初始化bean
     * 从 earlySingletonObjects 取出bean
     * 1. 给 bean 的成员变量注入对象
     * 2. 调用 @PostConstruct 方法
     * 3. 调用 @Bean 方法
     *
     * 将该bean 从 earlySingletonObjects 移除
     * 将 该 bean 放入到 singletonObjects中
     */
    public void handle() throws Exception {
        int size = 0;
        int count = 1;
        /*
         * 如果成功执行 @Bean注解修饰的方法，放入到该map中，值为true
         * 如果没有执行 @Bean注解修饰的方法，放入到该map中，值为false
         * key：类名_方法名_形参1类型名_形参2类型名_。。。。
         * value：true表示已经调用，false表示未调用
         */
        Map<String,Boolean> beanAnnMethods = new HashMap<>();
        while(true){
            int earlySingletonObjectSize = registry.getEarlySingletonObjectSize();
            // 二级缓存没有数据，说明注入完成
            // beanAnnMethods所有的value都为true，说明所有的 @Bean方法都执行
            boolean beanAnnMethodsValues = beanAnnMethods.values().stream().allMatch(x -> x);
            if(earlySingletonObjectSize == 0 && beanAnnMethodsValues){
                break;
            }
            // 如果二级缓存数量与上次数量相同，说明无法处理依赖，则终止
            if(size == earlySingletonObjectSize){
                LOG.error("无法处理依赖，终止运行！");
                break;
            }
            size = earlySingletonObjectSize;
            registry.forEachClassInfos(classInfo -> {
                String beanName = classInfo.getName();
                Object bean = registry.getEarlySingletonObject(beanName);
                if(bean==null){
                    return;
                }
                resolveField(classInfo, bean);

                if(doneInjectField(classInfo,bean)) {
                    invokePostConstructMethods(bean);
                    boolean res = invokeBeanAnnotationMethods(classInfo, bean,beanAnnMethods);
                    if(res) {
                        registry.registerSingletonObject(beanName, bean);
                    }
                }
            });
            count++;
        }

        LOG.debug("注入执行 "+count+" 次循环");

        check();
    }
    /**
     * 校验初始化，如果 earlySingletonObjects 还有数据，就报错
     */
    private void check() throws Exception {
        registry.forEachEarlySingletonObjects((beanName,bean)->{
            ClassInfo classInfo = registry.getClassInfoByName(beanName);
            // 处理 Autowired 注解
            for (FieldInfo fi : classInfo.getFieldInfos()) {
                if(fi.getAutowired() == null){
                    continue;
                }
                Field field = fi.getField();
                // 获取该bean的成员变量的值
                field.setAccessible(true);
                Object fieldValue = field.get(bean);
                // 如果该 bean的成员变量的值 为null，说明没有完成
                if(fieldValue == null){
                    throw new BeanCreationException(beanName,field.getName());
                }

            }
            // 处理 Resource 注解
            for (FieldInfo fi : classInfo.getFieldInfos()) {
                if(fi.getResource() == null){
                    continue;
                }
                Field field = fi.getField();
                // 获取该bean的成员变量的值
                field.setAccessible(true);
                Object fieldValue = field.get(bean);
                // 如果该 bean的成员变量的值 不为null，说明没有完成
                if (fieldValue == null) {
                    throw new BeanCreationException(beanName,field.getName());
                }
            }
        });

    }




    /**
     * 判断是否完成注入成员变量
     * @param classInfo
     * @return
     */
    private boolean doneInjectField(ClassInfo classInfo,Object instance) throws IllegalAccessException {

//        if ( !classInfo.isComponentAnnotation()) {
//            return false;
//        }
        for (FieldInfo fi : classInfo.getFieldInfos()) {
            if(fi.getAutowired() == null){
                continue;
            }
            Field field = fi.getField();
            // 处理 Autowired 注解
            // 获取该bean的成员变量的值
            field.setAccessible(true);
            Object fieldValue = field.get(instance);
            // 如果该 bean的成员变量的值 为null，说明没有完成
            if(fieldValue == null){
                return false;
            }

        }
        for (FieldInfo fi : classInfo.getFieldInfos()) {
            if(fi.getResource() == null){
                continue;
            }
            Field field = fi.getField();
            // 处理 Resource 注解

            // 获取该bean的成员变量的值
            field.setAccessible(true);
            Object fieldValue = field.get(instance);
            // 如果该 bean的成员变量的值 不为null，说明没有完成
            if (fieldValue == null) {
                return false;
            }
        }
        return true;
    }
    /**
     * 分析成员变量有 @Autowired 或 @Resource 注解，就执行注入
     */
    private void resolveField(ClassInfo classInfo,Object instance) throws IllegalAccessException, InvocationTargetException {

        String beanName = classInfo.getName();

        if ( !AnnotationFilter.hasClassAnnotation(classInfo.getClazz()) ) {
            return;
        }
        for (FieldInfo fi : classInfo.getFieldInfos()) {
            if(fi.getAutowired() == null){
                continue;
            }
            Field field = fi.getField();
            // 处理 Autowired 注解
//            Autowired anno = field.getAnnotation(Autowired.class);
            // 获取该bean的成员变量的值
            field.setAccessible(true);
            Object fieldValue = field.get(instance);
            // 如果该 bean的成员变量的值 不为null，说明已经注入过，那么就不继续执行
            if(fieldValue!=null){
                continue;
            }
            injectByFieldClass(beanName,instance,field);

        }
        for (FieldInfo fi : classInfo.getFieldInfos()) {
            if(fi.getResource() == null){
                continue;
            }
            Field field = fi.getField();
            // 处理 Resource 注解

            // 获取该bean的成员变量的值
            field.setAccessible(true);
            Object fieldValue = field.get(instance);
            // 如果该 bean的成员变量的值 不为null，说明已经注入过，那么就不继续执行
            if(fieldValue!=null){
                continue;
            }
            // 处理 Resource 注解
            Resource anno = field.getAnnotation(Resource.class);
            String name = anno.name().trim();
            Class<?> type = anno.type();
            // @Resource 注解的type默认是 Object.class 类型，所以为了正常运行，这里赋值为null
            if(type == Object.class){
                type = null;
            }
            // 如果注解中name、type属性不为空
            if(name!=null && !"".equals(name) && type!=null){
                Object o = registry.getSingletonObject(name);
                // 如果从bean中没有获取到对象，则不处理，后面还处理@Bean注解，之后再次处理依赖
                if(o == null){

                }else if(o.getClass() == type){
                    try {
                        field.set(instance, o);
                    } catch (IllegalAccessException e) {
                        throw e;
                    }
                }else{
                    throw new BeanCreationException(beanName,field.getDeclaringClass().getName(),type.getName());
                }

            }else if(name!=null && !"".equals(name)){ // 如果注解中name属性不为空

                Object o = registry.getSingletonObject(name);
                // 如果从bean中没有获取到对象，则不处理，后面还处理@Bean注解，之后再次处理依赖
                if(o!=null) {
                    try {
                        field.set(instance, o );
                    } catch (IllegalAccessException e) {
                        throw e;
                    }
                }
            }else if(type!=null){ // 如果注解中 type 不为空
                injectByFieldClass(beanName,instance,field);
            }else { // 如果注解中 name、type属性都为空
                String fieldName = field.getName();
                Object bean = registry.getBeanByName(fieldName);
                if(bean!=null) {
                    try {
                        field.set(instance, bean);
                    } catch (IllegalAccessException e) {
                        throw e;
                    }
                }else{

                    injectByFieldClass(beanName,instance,field);

                }
            }

        }
    }


    /**
     * 根据成员变量的类型进行注入
     * @param beanName
     * @param bean
     * @param field
     */
    private void injectByFieldClass(String beanName,Object bean,Field field) throws IllegalAccessException, InvocationTargetException {

        Class<?> fieldType = field.getType();
        Tuple2<String[],Object[]> tuple2 = registry.getBeanByType(fieldType);
        Object[] beans = tuple2.getSecond();
        // 如果从bean中没有获取到对象，则不处理，后面还处理@Bean注解，之后再次处理依赖
        if(beans == null || beans.length == 0){
//            resolverBeanAnnotationByFieldType(fieldType);
//            throw new BeanCreationException(beanName,fieldType.getName());
        }else if(beans.length>1){
            throw new BeanCreationException(beanName,fieldType.getName(),beans.length,tuple2.getFirst());
        }else{
            field.set(bean, beans[0]);
        }
    }

    /**
     * 分析出 @Bean 注解修饰的方法的返回类型，与 形参类型（成员变量）相匹配
     * 那么对 @Bean 方法的所属类，进行注入成员变量
     * @param fieldType
     */
    private void resolverBeanAnnotationByFieldType(Class fieldType) throws Exception {
        registry.forEachClassInfos(classInfo -> {
            List<MethodInfo> beanAnnotationMethodInfos = classInfo.getBeanAnnotationMethodInfos();
            for(MethodInfo methodInfo :beanAnnotationMethodInfos){
                Method method = methodInfo.getMethod();
                Class<?> returnType = method.getReturnType();
                if(returnType == fieldType){

                    Object bean = registry.getBeanByName(classInfo.getName());
                    resolveField(classInfo,bean);
                }
            }
        });
    }

    /**
     * 调用 @Bean 注解修饰的方法，并将返回结果放入到 singletonObjects 中
     * @param classInfo
     * @param instance
     * @throws IllegalAccessException
     * @throws InvocationTargetException
     * @return true：表示全部调用；false：表示有未调用的方法
     */
    private boolean invokeBeanAnnotationMethods(ClassInfo classInfo,Object instance,Map<String,Boolean> beanAnnMethods) throws Exception {

        List<MethodInfo> beanAnnotationMethodInfos = classInfo.getBeanAnnotationMethodInfos();
        for(MethodInfo methodInfo :beanAnnotationMethodInfos){
            Method method = methodInfo.getMethod();
            String methodName = ClassUtil.getMethodName(classInfo.getClazz(),method);

            // 不包含该方法形参名，说明第一次执行，放入初始数据，false表示没有调用过
            if(!beanAnnMethods.containsKey(methodName)){
                beanAnnMethods.put(methodName,false);

            }else{
                // true表示调用过
                if(beanAnnMethods.get(methodName)){
                    continue;
                }

            }
            //判断是否准备好调用 @Bean 方法
            boolean ready = false;
            /*
             * 取出方法的形参类型
             * 并从 registry 取出相匹配的类型的对象，然后进行下面处理：
             * 1.如果没有匹配类型的对象，就将 bean 放入到 beanAnnMethods 中
             * 2.如果同一个类型取出多个对象，则报错
             * 3. 如果全部能够取出相匹配的所有数据，就调用 @Bean 方法
             */
            Class<?>[] parameterTypes = method.getParameterTypes();
            Object[] parameters = new Object[parameterTypes.length];
            if (parameterTypes.length == 0){
                ready = true;
            }else {
                for (int i = 0; i < parameterTypes.length; i++) {
                    Class<?> clazz = parameterTypes[i];
                    Tuple2<String[], Object[]> tuple2 = registry.getBeanByType(clazz);
                    Object[] beans = tuple2.getSecond();
                    // 如果从bean中没有获取到对象，则不处理
                    if (beans == null || beans.length == 0) {
                        ready = false;
                        break;
//            resolverBeanAnnotationByFieldType(fieldType);
//            throw new BeanCreationException(beanName,fieldType.getName());
                    } else if (beans.length > 1) {
                        throw new Exception(classInfo.getName() + " 的 " + method.getName() + " 方法的 " + clazz.getName() + " 类型的对象有多个！");
                    } else {
                        ready = true;
                        parameters[i] = beans[0];
                    }
                }
            }

            if(ready) {

                method.setAccessible(true);
                Object res = method.invoke(instance, parameters);
                String[] names = methodInfo.getBeanAnnotationInfo().getName();
                for (String name : names) {
                    registry.registerSingletonObject(name, res);
                }
                beanAnnMethods.put(methodName,true);
            }


        }

        String namePrefix = classInfo.getClazz().getName()+".";
        for(String key : beanAnnMethods.keySet()){
            if(key.startsWith(namePrefix)){
                Boolean value = beanAnnMethods.get(key);
                if(!value){
                    return value;
                }
            }
        }
        return true;
    }
    /**
     * 调用 @Bean 注解修饰的方法，并将返回结果放入到 singletonObjects 中
     */
//    private void invokeBeanAnnotationMethod(MethodInfo methodInfo,Object bean) throws InvocationTargetException, IllegalAccessException {
//        Method method = methodInfo.getMethod();
//        Object res = method.invoke(bean);
//        String[] names = methodInfo.getBeanAnnotationInfo().getName();
//        for(String name : names) {
//            singletonObjects.put(name,res);
//        }
//    }


    /**
     * 调用 @PostConstruct  注解修饰的方法
     * @param bean
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     */
    private void invokePostConstructMethods(Object bean) throws InvocationTargetException, IllegalAccessException {
        Method[] methods = bean.getClass().getDeclaredMethods();
        for(Method method : methods){

            if(method.isAnnotationPresent(PostConstruct.class)){
                method.setAccessible(true);
                method.invoke(bean);
            }
        }
    }




}
